#include "Avalon/Network/TcpSocketConnection.hpp"
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include "Avalon/Network/SocketException.hpp"
#include "Avalon/Network/SocketType.hpp"
#include "Avalon/Network/TcpSocketReader.hpp"
#include "Avalon/Network/TcpSocketWriter.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Network;
using namespace Avalon::Network::Detail;
using namespace Avalon::Threading;
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace boost::posix_time;
using namespace std;

TcpSocketConnection::~TcpSocketConnection() {
  Close();
}

void TcpSocketConnection::Open() {
  lock_guard<mutex> lock(m_mutex);
  if(m_isOpen) {
    return;
  }
  tcp::resolver resolver(m_socket->m_socket.get_io_service());
  tcp::resolver::query query(m_address.first,
    lexical_cast<string>(m_address.second));
  system::error_code error;
  tcp::resolver::iterator end;
  tcp::resolver::iterator endpointIterator = resolver.resolve(query, error);
  if(error != 0) {
    BOOST_THROW_EXCEPTION(SocketException(error.value(), error.message()));
  }
  error = error::host_not_found;
  while(error != 0 && endpointIterator != end) {
    system::error_code closeError;
    m_socket->m_socket.close(closeError);
    m_socket->m_socket.connect(*endpointIterator, error);
    ++endpointIterator;
  }
  if(error != 0) {
    BOOST_THROW_EXCEPTION(SocketException(error.value(), error.message()));
  }
  m_isOpen = true;
}

bool TcpSocketConnection::IsOpen() const {
  lock_guard<mutex> lock(m_mutex);
  return m_isOpen;
}

void TcpSocketConnection::Close() {
  shared_ptr<ClosedSlot> closedSlot;
  {
    lock_guard<mutex> lock(m_mutex);
    if(!m_isOpen) {
      return;
    }
    system::error_code error;
    m_socket->m_socket.shutdown(tcp::socket::shutdown_both, error);
    if(error != 0) {
      BOOST_THROW_EXCEPTION(SocketException(error.value(), error.message()));
    }
    m_socket->m_socket.close(error);
    if(error != 0) {
      BOOST_THROW_EXCEPTION(SocketException(error.value(), error.message()));
    }
    m_isOpen = false;
    closedSlot = m_closedSlot;
  }
  (*closedSlot)();
}

void TcpSocketConnection::SetClosedSlot(const ClosedSlot& slot,
    Out<bool> isOpen) {
  lock_guard<mutex> lock(m_mutex);
  *isOpen = m_isOpen;
  m_closedSlot.reset(new ClosedSlot(slot));
}

TcpSocketConnection::TcpSocketConnection(bool isOpen, const IpAddress& address,
    const shared_ptr<SocketType>& socket,
    const shared_ptr<TcpSocketReader>& reader,
    const shared_ptr<TcpSocketWriter>& writer)
    : m_isOpen(isOpen),
      m_address(address),
      m_socket(socket),
      m_reader(reader),
      m_writer(writer) {}
